function copyStyle(src: HTMLElement, dest: HTMLElement) {
  const style = window.getComputedStyle(src);
  const destStyle = window.getComputedStyle(dest);
  for (let i = 0; i < style.length; i++) {
    const key = style[i];
    if (destStyle[key as any] !== style[key as any])
      dest.style[key as any] = style[key as any];
  }
}
function cloneNode(dom: HTMLElement): HTMLElement | undefined {
  if (dom instanceof HTMLIFrameElement) {
    const html = dom.contentDocument?.documentElement.outerHTML;
    if (!html) return;
    // 去除html里面的js代码
    const newHtml = html.replace(
      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
      ""
    );
    const newDom = document.createElement("iframe");
    newDom.srcdoc = newHtml;
    return newDom;
  } else if (dom instanceof HTMLCanvasElement) {
    const newDom = document.createElement("canvas");
    const ctx = newDom.getContext("2d");
    if (!ctx) return;
    newDom.width = dom.width;
    newDom.height = dom.height;
    ctx.drawImage(dom, 0, 0);
    return newDom;
  }
  // 如果是图片
  else if (dom instanceof HTMLImageElement) {
    const newDom = document.createElement("img");
    dom.removeAttribute("crossOrigin");
    newDom.removeAttribute("crossOrigin");
    newDom.loading = "eager";
    newDom.src = dom.src;
    return newDom;
  }
  if (dom.children.length === 0) return dom.cloneNode(true) as HTMLElement;
  const children = [...dom.children] as HTMLElement[];
  const newDom = dom.cloneNode(false) as HTMLElement;
  children.forEach((item) => {
    const clone = cloneNode(item);
    if (clone) {
      newDom.appendChild(clone);
    }
  });
  return newDom;
}
const duration = 500;
const sharDomKey = "share";
// const animationCurve = 'cubic-bezier(.54,.33,.4,.96)'
const animationCurve = "cubic-bezier(.2,0,.2,1)";
const containerIncludeStyle: Array<keyof CSSStyleDeclaration> = [
  "backgroundColor",
  // "borderRadius",
];
class ShareItem {
  startDom: HTMLElement | undefined;
  endDom: HTMLElement | undefined;
  startRect: DOMRect | undefined;
  endRect: DOMRect | undefined;
  startOriginDom: HTMLElement | undefined;
  endOriginDom: HTMLElement | undefined;
  constructor(dom: HTMLElement) {
    if (!(dom instanceof HTMLElement)) {
      throw new Error("ShareItem: dom is not HTMLElement");
    }
    this.start(dom);
  }
  start(dom: HTMLElement) {
    this.startDom = cloneNode(dom);
    copyStyle(dom, this.startDom as HTMLElement);
    this.startRect = dom.getBoundingClientRect();
    this.startOriginDom = dom;
  }
  end(dom: HTMLElement) {
    this.endDom = cloneNode(dom);
    copyStyle(dom, this.endDom as HTMLElement);
    this.endOriginDom = dom;
    this.endRect = dom.getBoundingClientRect();
  }
  play(reverse = false) {
    return new Promise((resolve, reject) => {
      const {
        startDom,
        endDom,
        startRect,
        endRect,
        endOriginDom,
        startOriginDom,
      } = this;
      const originDom = !reverse ? endOriginDom : startOriginDom;
      if (!startDom || !endDom || !startRect || !endRect) {
        if (originDom) {
          originDom.style.opacity = "1";
        }
        reject("ShareItem: play fail");
        return;
      }
      if (originDom) {
        originDom.style.opacity = "0";
      }
      const dom = reverse ? startDom : endDom;
      document.body.appendChild(dom);
      const _startRect = reverse ? endRect : startRect;
      const _endRect = reverse ? startRect : endRect;
      dom.style.position = "fixed";
      dom.style.left = "0px";
      dom.style.top = "0px";
      dom.style.zIndex = Number.MAX_SAFE_INTEGER + "";
      dom.style.opacity = "1";
      const startTransform = `translate(${_startRect.left}px,${_startRect.top}px)`; //translate(378.21875px,40.234375px)
      const endTransform = `translate(${_endRect.left}px,${_endRect.top}px)`; //translate(971.234375px,43.671875px)
      dom
        .animate(
          [
            {
              transform: startTransform,
            },
            {
              transform: endTransform,
            },
          ],
          {
            duration,
            easing: animationCurve,
          }
        )
        .finished.then(() => {
          if (originDom) {
            originDom.style.opacity = "1";
          }
          dom.remove();
          resolve(undefined);
        });
    });
  }
}
class ShareAnimation {
  startElement: HTMLElement | undefined;
  endElement: HTMLElement | undefined;
  startRect: DOMRect | undefined;
  startStyle: CSSStyleDeclaration | undefined;
  startOtherChildren: HTMLElement[] = [];
  endRect: DOMRect | undefined;
  endStyle: CSSStyleDeclaration | undefined;
  endOtherChildren: HTMLElement[] = [];
  childMap: Map<string, ShareItem> = new Map();
  constructor() {}
  setStartBody(dom: HTMLElement) {
    if (!(dom instanceof HTMLElement))
      throw new Error("ShareAnimation: dom is not HTMLElement");
    this.startElement = dom;
    this.startRect = dom.getBoundingClientRect();
    this.startStyle = window.getComputedStyle(dom);
    dom.querySelectorAll("*").forEach((item) => {
      if (!(item instanceof HTMLElement)) return;
      const key = item.getAttribute(sharDomKey);
      if (!key) {
        const children = item.querySelector(`[${sharDomKey}]`);
        if (children) return;
        const position = window.getComputedStyle(item).position;
        if (
          position === "fixed" ||
          position === "sticky" ||
          position === "absolute"
        )
          return;
        this.startOtherChildren.push(item);
        return;
      }
      this.childMap.set(key, new ShareItem(item));
    });
  }
  setEndBody(dom: HTMLElement) {
    if (!(dom instanceof HTMLElement))
      throw new Error("ShareAnimation: dom is not HTMLElement");
    this.endElement = dom;
    this.endRect = dom.getBoundingClientRect();
    this.endStyle = window.getComputedStyle(dom);
    dom.querySelectorAll(`*`).forEach((item) => {
      if (!(item instanceof HTMLElement)) return;
      const key = item.getAttribute(sharDomKey);
      if (!key) {
        const children = item.querySelector(`[${sharDomKey}]`);
        if (children) return;
        const position = window.getComputedStyle(item).position;
        if (
          position === "fixed" ||
          position === "sticky" ||
          position === "absolute"
        )
          return;
        this.endOtherChildren.push(item);
        return;
      }
      const shareElement = this.childMap.get(key);
      if (shareElement) {
        shareElement.end(item);
      }
    });
  }
  play(reverse = false) {
    if (
      !(this.startElement && this.endElement && this.startRect && this.endRect)
    ) {
      return Promise.reject("ShareAnimation: dom is not HTMLElement");
    }
    return new Promise((resolve, reject) => {
      let finishCount = 0;
      this.playBackground(reverse);
      const { childMap } = this;
      function end() {
        finishCount++;
        if (finishCount === childMap.size) {
          resolve(undefined);
        }
      }
      this.childMap.forEach((item) => {
        item
          .play(reverse)
          .catch((err) => {
            console.error(err);
          })
          .finally(() => {
            end();
          });
      });
    });
  }
  // getClipPath(domRect: DOMRect, rect: DOMRect) {
  //   const { top, right, left, bottom } = domRect;
  //   const clipPath = `inset(${Math.abs(top - rect.top)}px ${Math.abs(
  //     right - rect.right
  //   )}px
  //     ${Math.abs(bottom - rect.bottom)}px ${Math.abs(left - rect.left)}px)`;
  //   return clipPath;
  // }
  playBody(reverse = false) {
    const { startElement, endElement } = this;
    if (!startElement || !endElement) {
      return;
    }
    if (!reverse) {
      startElement.style.opacity = "0";
      endElement.style.opacity = "1";
      return;
    }
    startElement.style.opacity = "1";
    endElement.style.opacity = "0";
  }
  useContainerStyle() {
    let { startStyle, endStyle } = this;
    if (!startStyle || !endStyle) throw new Error("ShareAnimation: no style");
    const start: Partial<CSSStyleDeclaration> = {};
    const end: Partial<CSSStyleDeclaration> = {};
    containerIncludeStyle.forEach((key) => {
      // @ts-ignore
      start[key] = startStyle[key];
      // @ts-ignore
      end[key] = endStyle[key];
    });
    return {
      start: {
        ...start,
      },
      end: {
        ...end,
      },
    };
  }
  useContainer() {
    let { startRect, endRect } = this;
    if (!startRect || !endRect) throw new Error("ShareAnimation: no rect");
    const { start, end } = this.useContainerStyle();
    const scaleX = endRect.width / startRect.width;
    const scaleY = endRect.height / startRect.height;
    const startCenterX = startRect.left + startRect.width / 2;
    const startCenterY = startRect.top + startRect.height / 2;
    const endCenterX = endRect.left + endRect.width / 2;
    const endCenterY = endRect.top + endRect.height / 2;
    const endTransformX = (endCenterX - startCenterX) / scaleX;
    const endTransformY = (endCenterY - startCenterY) / scaleY;
    return {
      start: {
        ...start,
        transform: `scale(1) translate3d(0px,0px,0px)`,
      },
      end: {
        ...end,
        transform: `scale(${scaleX},${scaleY}) translate3d(${endTransformX}px,${endTransformY}px,${Math.max(
          scaleX,
          scaleY
        )}px)`,
      },
    };
  }
  playBackground(reverse = false) {
    return new Promise((resolve, reject) => {
      let { startRect, startStyle, endStyle, startElement, endElement } = this;
      if (
        !startRect ||
        !startStyle ||
        !endStyle ||
        !startElement ||
        !endElement
      ) {
        reject("ShareAnimation: dom is not HTMLElement");
        return;
      }
      if (reverse) [startElement, endElement] = [endElement, startElement];
      startElement.style.opacity = "0";
      endElement.style.opacity = "0";
      const tempDom = document.createElement("div");
      tempDom.style.zIndex = Number.MAX_SAFE_INTEGER - 1 + "";
      tempDom.style.position = "fixed";
      tempDom.style.left = startRect.left + "px";
      tempDom.style.top = startRect.top + "px";
      tempDom.style.width = startRect.width + "px";
      tempDom.style.height = startRect.height + "px";
      let { start, end } = this.useContainer();
      if (reverse) [start, end] = [end, start];
      document.body.appendChild(tempDom);
      // @ts-ignore
      tempDom.animate([start, end], {
        duration: duration * 0.7,
        easing: animationCurve,
        fill: "forwards", // 保持最后一帧
      }).onfinish = () => {
        // this.playBody(reverse);
        if (endElement) endElement.style.opacity = "1";
        tempDom.remove();
        let { startOtherChildren, endOtherChildren } = this;
        if (reverse)
          [startOtherChildren, endOtherChildren] = [
            endOtherChildren,
            startOtherChildren,
          ];
        endOtherChildren.forEach((item) => {
          let { height } = item.getBoundingClientRect();
          if (height > 100) height = 100;
          item.animate(
            [
              {
                transform: `translateY(${height}px)`,
                opacity: 0,
              },
              {
                transform: "translateY(0%)",
                opacity: 1,
              },
            ],
            {
              duration: duration,
              easing: animationCurve,
            }
          );
        });
        resolve(undefined);
      };
    });
  }
}
let shareAnimation: ShareAnimation | undefined;
export function startShareAnimation(dom: HTMLElement) {
  shareAnimation = new ShareAnimation();
  shareAnimation.setStartBody(dom);
}

export function endShareAnimation(dom: HTMLElement) {
  if (!shareAnimation?.startElement) {
    return;
  }
  shareAnimation.setEndBody(dom);
  return shareAnimation.play();
}

export function reverseShareAnimation() {
  if (!shareAnimation?.startElement || !shareAnimation?.endElement) {
    return;
  }
  return shareAnimation.play(true).then(() => {
    shareAnimation = undefined;
  });
}

export function clearShareAnimation() {
  shareAnimation = undefined;
}
